package com.gitee.vueantd.upload.service.impl;

import com.gitee.vueantd.common.utils.ElAdminConstant;
import com.gitee.vueantd.upload.domain.UploadChunk;
import com.gitee.vueantd.upload.repository.UploadChunkRepository;
import com.gitee.vueantd.upload.service.UploadTaskDataService;
import com.gitee.vueantd.upload.service.dto.ChunkRequest;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;

import javax.xml.bind.DatatypeConverter;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.Callable;

/**
 * @Author:
 * @Date: 2021/1/7 13:17
 * @Description: 文件上传辅助计算类
 */

@Log4j2
public class FileUploadCallable implements Callable<Boolean> {
  private UploadTaskDataService uploadTaskDataService;
  private UploadChunkRepository uploadChunkRepository;
  private ChunkRequest chunkRequest;
  private File file;
  private Long contentLength;

  public FileUploadCallable(UploadTaskDataService uploadTaskDataService,
                            UploadChunkRepository uploadChunkRepository,
                            ChunkRequest chunkRequest,
                            File file, Long contentLength) {
    this.uploadTaskDataService = uploadTaskDataService;
    this.uploadChunkRepository = uploadChunkRepository;
    this.chunkRequest = chunkRequest;
    this.file = file;
    this.contentLength = contentLength;
  }

  @Override
  public Boolean call() throws Exception {
    long start = System.currentTimeMillis();
    String md5String = getChunkHash(chunkRequest, file, contentLength);
    if (StringUtils.equalsAnyIgnoreCase(md5String, chunkRequest.chunkHash)) {
      UploadChunk uploadChunk = new UploadChunk();
      uploadChunk.setChunkIndex(chunkRequest.chunkIndex);
      uploadChunk.setChunkSize(chunkRequest.chunkSize);
      uploadChunk.setFileSize(chunkRequest.fileSize);
      uploadChunk.setFileName(chunkRequest.fileName);
      uploadChunk.setTotalChunks(chunkRequest.totalChunks);
      uploadChunk.setUserName(chunkRequest.userName);
      uploadChunk.setFileHash(chunkRequest.fileHash);
      uploadChunk.setNodeId(chunkRequest.nodeId);
      uploadChunk.setChunkHash(chunkRequest.chunkHash);
      uploadChunk.setFileType(chunkRequest.fileType);
      uploadChunk.setModifyTime(chunkRequest.modifyTime);
      uploadChunk = uploadChunkRepository.save(uploadChunk);

      if (chunkRequest.chunkIndex.equals(chunkRequest.totalChunks)) {
        file.setLastModified(uploadChunk.getModifyTime());
        String ext = FilenameUtils.getExtension(uploadChunk.getFileName());
        if (StringUtils.isNotEmpty(ext)) {
          ext = "." + ext;
        } else {
          ext = "";
        }
        String newFileName = chunkRequest.fileHash + ext;
        File uploadedFile = FileUtils.getFile(file.getParentFile(), newFileName);
        file.renameTo(uploadedFile);
        uploadTaskDataService.add(uploadChunk, uploadedFile, newFileName);
      }
    } else {
      log.warn("file: {}, hash is not the same, {}, {}", chunkRequest.fileHash, md5String, chunkRequest.chunkHash);
      throw new IOException("hash is not same");
    }

    log.info("file: {}, cal md5 cost: {}", chunkRequest.getFileHash(), System.currentTimeMillis() - start);
    return Boolean.TRUE;
  }

  private String getChunkHash(ChunkRequest chunkRequest, File file, long contentLength) throws NoSuchAlgorithmException, IOException {
    byte[] bytes = new byte[ElAdminConstant.UPLOAD_BUFFER_SIZE];
    MessageDigest digest = MessageDigest.getInstance("MD5");
    String md5String = "";
    try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {
      randomAccessFile.seek((chunkRequest.chunkIndex - 1) * chunkRequest.chunkSize);
      long readed = 0;
      while (readed < contentLength) {
        int len = (int) Math.min(contentLength - readed, bytes.length);
        int r = randomAccessFile.read(bytes, 0, len);
        if (r < 0) {
          break;
        }
        digest.update(bytes, 0, r);
        readed += r;
      }
      byte[] md5sum = digest.digest();
      md5String = DatatypeConverter.printHexBinary(md5sum).toLowerCase();
    }
    return md5String;
  }
}
